package com.fjh.security.easy.starter.userdetails;

import com.fjh.security.easy.starter.config.EasyPropertiesConfig;
import es.moki.ratelimitj.core.limiter.request.RequestRateLimiter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.context.MessageSourceAware;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

import java.sql.SQLException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toList;

/**
 * @author fanjh37
 * @since 2023/2/1 10:12
 */
@Slf4j
public class EasyDefaultJdbcDaoImpl extends JdbcDaoSupport implements MessageSourceAware, UserDetailsService {
    protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

    @Autowired
    private EasyPropertiesConfig easyPropertiesConfig;

    @Autowired
    private RequestRateLimiter requestRateLimiter;

    @Override
    public void setMessageSource(MessageSource messageSource) {
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<CustomUser> users = this.loadUsersByUsername(username);
        if (users.size() == 0) {
            throw new UsernameNotFoundException(this.messages.getMessage("JdbcDaoImpl.notFound", new Object[]{username}, "Username {0} not found"));
        }
        CustomUser user = users.get(0);
        user.setAuthorities(getAuthorityByUsername(user.getId()));
        return user;
    }

    protected List<GrantedAuthority> getAuthorityByUsername(String id) {
        return getJdbcTemplate().query(easyPropertiesConfig.getUserDetails().authoritiesByIdQuery, new String[]{id}, (rs, rowNum) -> {
            String authority = rs.getString(1);
            return new SimpleGrantedAuthority(authority);
        });
    }

    protected List<CustomUser> loadUsersByUsername(String username) {
        String[] selectField = getUserByUsernameQuery().substring(getUserByUsernameQuery().lastIndexOf("select") + 6, getUserByUsernameQuery().lastIndexOf("from")).split(",");
        RowMapper<CustomUser> mapper = (rs, rowNum) -> {
            String username1 = rs.getString(baseSelectField.get(1));
            String id = Objects.isNull(rs.getString(baseSelectField.get(0))) ? username1 : rs.getString(baseSelectField.get(0));
            String password = rs.getString(baseSelectField.get(2));
            boolean enabled = Objects.isNull(rs.getBoolean(baseSelectField.get(3))) ? true : rs.getBoolean(baseSelectField.get(3));
            boolean unLock = getNonLock(username);
            CustomUser customUser = new CustomUser(id, username1, password, enabled, unLock, AuthorityUtils.NO_AUTHORITIES);
            //额外字段
            Arrays.stream(selectField).filter(x -> !baseSelectField.contains(x.trim())).forEach(x -> {
                try {
                    customUser.putExtParamByName(x.trim(), rs.getString(x.trim()));
                } catch (SQLException e) {
                    log.error("e: {}", e.getMessage());
                }
            });
            return customUser;
        };
        return this.getJdbcTemplate().query(getUserByUsernameQuery(), mapper, new Object[]{username});
    }

    private boolean getNonLock(String username) {
        return !requestRateLimiter.geLimitWhenIncremented(username, 0);
    }


    private List<String> baseSelectField = Stream.of("id", "username", "password", "enabled").collect(toList());


    private String getUserByUsernameQuery() {
        return easyPropertiesConfig.getUserDetails().getUsersByUsernameQuery();
    }
}
